package com.anyext.storage.config;

import java.util.List;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import com.anyext.storage.exception.ParseException;
import com.anyext.storage.mapping.impl.AssociationImpl;
import com.anyext.storage.mapping.impl.AttributeImpl;
import com.anyext.storage.mapping.impl.BaseAttributeImpl;
import com.anyext.storage.mapping.impl.BusinessImpl;
import com.anyext.storage.mapping.impl.CollectionImpl;
import com.anyext.storage.mapping.impl.PrimaryKeyImpl;
import com.anyext.storage.mapping.impl.ResultMapImpl;
import com.anyext.storage.mapping.impl.SqlMapImpl;
import com.anyext.storage.policy.SqlType;

public class MappingDefinitionParser extends AbstractBeanDefinitionParser {

	private enum LocalName {
		business, select, insert, update, delete;
	}

	@Override
	protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
		List<Element> childElements = DomUtils.getChildElements(element);
		for (Element childElement : childElements) {
			if (LocalName.business.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseBusinessInternal(childElement, parserContext);
			} else if (LocalName.select.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseCustomSqlInternal(childElement, parserContext, SqlType.Select);
			} else if (LocalName.insert.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseCustomSqlInternal(childElement, parserContext, SqlType.Insert);
			} else if (LocalName.update.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseCustomSqlInternal(childElement, parserContext, SqlType.Update);
			} else if (LocalName.delete.name().equalsIgnoreCase(childElement.getLocalName())) {
				parseCustomSqlInternal(childElement, parserContext, SqlType.Delete);
			}
		}
		return null;
	}

	private void parseBusinessInternal(Element element, ParserContext parserContext) {
		RootBeanDefinition businessDefinition = new RootBeanDefinition();
		businessDefinition.setBeanClass(BusinessImpl.class);
		String attribute = element.getAttribute(ID_ATTRIBUTE);
		if (!StringUtils.hasText(attribute)) {
			attribute = element.getAttribute("class");
		}
		BeanDefinitionHolder idHolder = new BeanDefinitionHolder(businessDefinition, attribute);
		BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());
		businessDefinition.getPropertyValues().add("identity", attribute);

		attribute = element.getAttribute("class");
		try {
			businessDefinition.getPropertyValues().add("beanClass", ClassUtils.forName(attribute, ClassUtils.getDefaultClassLoader()));
		} catch (Throwable e) {
			throw new ParseException("can't load class[" + attribute + "] for mapping business[" + element.getAttribute(ID_ATTRIBUTE) + "]");
		}

		attribute = element.getAttribute("table");
		if (StringUtils.hasText(attribute)) {
			businessDefinition.getPropertyValues().add("table", attribute);
		}

		attribute = element.getAttribute("group");
		if (!StringUtils.hasText(attribute)) {
			attribute = ((Element) element.getParentNode()).getAttribute("group");
		}
		businessDefinition.getPropertyValues().add("group", new RuntimeBeanReference(attribute));

		attribute = element.getAttribute("router");
		if (StringUtils.hasText(attribute)) {
			businessDefinition.getPropertyValues().add("router", new RuntimeBeanReference(attribute));
		}

		Element primaryKeyElement = DomUtils.getChildElementByTagName(element, "id");
		RootBeanDefinition primaryKeyDefinition = new RootBeanDefinition();
		primaryKeyDefinition.setBeanClass(PrimaryKeyImpl.class);
		parsePrimaryKeyInternal(primaryKeyElement, primaryKeyDefinition);
		businessDefinition.getPropertyValues().add("primaryKey", primaryKeyDefinition);

		List<Element> attributeElements = DomUtils.getChildElementsByTagName(element, "attribute");
		if (attributeElements.size() > 0) {
			ManagedMap<String, BeanDefinition> attributesDefinition = new ManagedMap<String, BeanDefinition>();
			for (Element attributeElement : attributeElements) {
				RootBeanDefinition attributeDefinition = new RootBeanDefinition();
				attributeDefinition.setBeanClass(AttributeImpl.class);

				parseAttributeInternal(attributeElement, attributeDefinition);

				attributesDefinition.put(attributeElement.getAttribute("property"), attributeDefinition);
			}
			businessDefinition.getPropertyValues().add("attributes", attributesDefinition);
		}
	}

	private void parseBaseAttributeInternal(Element baseAttributeElement, RootBeanDefinition baseAttributeDefinition) {
		String attribute = baseAttributeElement.getAttribute("property");
		baseAttributeDefinition.getPropertyValues().add("property", attribute);
		attribute = baseAttributeElement.getAttribute("column");
		baseAttributeDefinition.getPropertyValues().add("column", attribute);
		attribute = baseAttributeElement.getAttribute("type");
		if (StringUtils.hasText(attribute)) {
			baseAttributeDefinition.getPropertyValues().add("type", attribute);
		}
	}

	private void parseAttributeInternal(Element attributeElement, RootBeanDefinition attributeDefinition) {
		parseBaseAttributeInternal(attributeElement, attributeDefinition);

		String attribute = attributeElement.getAttribute("insert");
		attributeDefinition.getPropertyValues().add("insert", attribute);
		attribute = attributeElement.getAttribute("update");
		attributeDefinition.getPropertyValues().add("update", attribute);
		attribute = attributeElement.getAttribute("lazy");
		attributeDefinition.getPropertyValues().add("lazy", attribute);
	}

	private void parsePrimaryKeyInternal(Element primaryKeyElement, RootBeanDefinition primaryKeyDefinition) {
		parseBaseAttributeInternal(primaryKeyElement, primaryKeyDefinition);

		Element generatorElement = DomUtils.getChildElementByTagName(primaryKeyElement, "generator");
		String attribute = generatorElement.getAttribute("class");
		primaryKeyDefinition.getPropertyValues().add("classType", attribute);

		List<Element> paramElements = DomUtils.getChildElements(generatorElement);
		if (paramElements.size() > 0) {
			ManagedMap<String, String> paramsMap = new ManagedMap<String, String>();
			for (Element paramElement : paramElements) {
				paramsMap.put(paramElement.getAttribute("name"), paramElement.getAttribute("value"));
			}
			primaryKeyDefinition.getPropertyValues().add("params", paramsMap);
		}
	}

	private void parseCustomSqlInternal(Element customSqlElement, ParserContext parserContext, SqlType sqlType) {
		RootBeanDefinition customSqlDescriptor = new RootBeanDefinition();
		customSqlDescriptor.setBeanClass(SqlMapImpl.class);

		String attribute = customSqlElement.getAttribute(ID_ATTRIBUTE);
		if (!StringUtils.hasText(attribute)) {
			throw new ParseException("must define a identity for mapping custom sql!");
		}
		BeanDefinitionHolder idHolder = new BeanDefinitionHolder(customSqlDescriptor, attribute);
		BeanDefinitionReaderUtils.registerBeanDefinition(idHolder, parserContext.getRegistry());
		customSqlDescriptor.getPropertyValues().add("identity", attribute);
		customSqlDescriptor.getPropertyValues().add("sqlType", sqlType);

		attribute = customSqlElement.getAttribute("group");
		if (!StringUtils.hasText(attribute)) {
			attribute = ((Element) customSqlElement.getParentNode()).getAttribute("group");
		}
		customSqlDescriptor.getPropertyValues().add("group", new RuntimeBeanReference(attribute));

		List<Element> paramElements = DomUtils.getChildElementsByTagName(customSqlElement, "param");
		if (paramElements.size() > 0) {
			ManagedMap<String, String> paramsMap = new ManagedMap<String, String>();
			for (Element paramElement : paramElements) {
				paramsMap.put(paramElement.getAttribute("alias"), paramElement.getAttribute("type"));
			}
			customSqlDescriptor.getPropertyValues().add("paramAlias", paramsMap);
		}

		Element primaryKeyElement = DomUtils.getChildElementByTagName(customSqlElement, "id");
		if (primaryKeyElement != null) {
			RootBeanDefinition primaryKeyDefinition = new RootBeanDefinition();
			primaryKeyDefinition.setBeanClass(PrimaryKeyImpl.class);

			parsePrimaryKeyInternal(primaryKeyElement, primaryKeyDefinition);

			customSqlDescriptor.getPropertyValues().add("primaryKey", primaryKeyDefinition);
		}

		Element sqlElement = DomUtils.getChildElementByTagName(customSqlElement, "sql");
		customSqlDescriptor.getPropertyValues().add("orginalSql", DomUtils.getTextValue(sqlElement));

		Element resultMapElement = DomUtils.getChildElementByTagName(customSqlElement, "resultMap");
		if (resultMapElement != null) {
			RootBeanDefinition resultMapDefinition = new RootBeanDefinition();
			resultMapDefinition.setBeanClass(ResultMapImpl.class);

			parseResultMapInternal(resultMapElement, resultMapDefinition);

			customSqlDescriptor.getPropertyValues().add("resultMap", resultMapDefinition);
		}
	}

	private void parseResultMapInternal(Element resultMapElement, RootBeanDefinition resultMapDefinition) {
		String attribute = resultMapElement.getAttribute("type");
		if (StringUtils.hasText(attribute)) {
			resultMapDefinition.getPropertyValues().add("type", attribute);
		}

		parseBaseAttributesInternal(resultMapElement, "id", resultMapDefinition, "ids");

		parseBaseAttributesInternal(resultMapElement, "result", resultMapDefinition, "results");

		List<Element> associationElements = DomUtils.getChildElementsByTagName(resultMapElement, "association");
		if (associationElements.size() > 0) {
			ManagedList<BeanDefinition> associationsDefinition = new ManagedList<BeanDefinition>();
			for (Element associationElement : associationElements) {
				RootBeanDefinition associationDefinition = new RootBeanDefinition();
				associationDefinition.setBeanClass(AssociationImpl.class);

				attribute = associationElement.getAttribute("type");
				associationDefinition.getPropertyValues().add("type", attribute);
				
				attribute = associationElement.getAttribute("property");
				associationDefinition.getPropertyValues().add("property", attribute);

				parseBaseAttributesInternal(associationElement, "id", associationDefinition, "ids");

				parseBaseAttributesInternal(associationElement, "result", associationDefinition, "results");

				associationsDefinition.add(associationDefinition);
			}
			resultMapDefinition.getPropertyValues().add("associations", associationsDefinition);
		}

		List<Element> collectionElements = DomUtils.getChildElementsByTagName(resultMapElement, "collection");
		if (collectionElements.size() > 0) {
			ManagedList<BeanDefinition> collectionsDefinition = new ManagedList<BeanDefinition>();
			for (Element collectionElement : collectionElements) {
				RootBeanDefinition collectionDefinition = new RootBeanDefinition();
				collectionDefinition.setBeanClass(CollectionImpl.class);

				attribute = collectionElement.getAttribute("type");
				collectionDefinition.getPropertyValues().add("type", attribute);

				attribute = collectionElement.getAttribute("property");
				collectionDefinition.getPropertyValues().add("property", attribute);

				parseBaseAttributesInternal(collectionElement, "id", collectionDefinition, "ids");

				parseBaseAttributesInternal(collectionElement, "result", collectionDefinition, "results");

				collectionsDefinition.add(collectionDefinition);
			}
			resultMapDefinition.getPropertyValues().add("collections", collectionsDefinition);
		}

	}

	private void parseBaseAttributesInternal(Element parentElement, String elementName, RootBeanDefinition parentDefinition, String propertyName) {
		List<Element> baseAttributeElements = DomUtils.getChildElementsByTagName(parentElement, elementName);
		if (baseAttributeElements.size() > 0) {
			ManagedList<BeanDefinition> baseAttributesDefinition = new ManagedList<BeanDefinition>();
			for (Element baseAttributeElement : baseAttributeElements) {
				RootBeanDefinition baseAttributeDefinition = new RootBeanDefinition();
				baseAttributeDefinition.setBeanClass(BaseAttributeImpl.class);

				parseBaseAttributeInternal(baseAttributeElement, baseAttributeDefinition);

				baseAttributesDefinition.add(baseAttributeDefinition);
			}
			parentDefinition.getPropertyValues().add(propertyName, baseAttributesDefinition);
		}
	}

}
